#include "InfoWindow.h"
#include <assert.h>

#define STACKLIST_ID       1
#define VARIABLEGRID_ID    2


static LPCTSTR GetTypeString(value_type_t type);
static value_type_t GetTypeFromString(LPCTSTR str);

CInfoWindow::CInfoWindow()
{
   m_hStackList = NULL;
   m_hexe = NULL;
   m_nCurRtnStartingLineNo = 0;
   m_nCurRtnEndingLineNo = 0;
   m_nStackIdx = -1;
}

CInfoWindow::~CInfoWindow()
{
}

HWND CInfoWindow::CreatePaneWindow(HWND hParent, RECT& rc, DWORD dwStyle, DWORD dwExStyle)
{
    assert (hParent);
    
    dwStyle |= WS_CHILD | WS_VISIBLE;
    HWND hWnd = Create(hParent, rc);

    if (!hWnd) {
        return NULL;
    }

    return m_hWnd;
}

#define BUFLEN 256

void CInfoWindow::GetStackInfo(exe_handle_t hexe)
{
   assert (hexe);

   m_hexe = hexe;
   int nIdx = -1;
   int ret;
   do {
      TCHAR rtn_name[BUFLEN];
      int lineno;
      ret = debug_get_call_stack (hexe, &nIdx, rtn_name, BUFLEN, &lineno);
      if (ret == 0 ) {
         AddStackItem (rtn_name, lineno);
      }
   } while (ret == 0);
   SendMessage(m_hStackList, CB_SETCURSEL, 0, 0);
   BOOL bFoo;
   OnStackSel(0, 0, 0, bFoo);
}

void CInfoWindow::ClearAllInfo()
{
   int nRows = m_gdVariables.GetRows();
   if (nRows > 1) {
      m_gdVariables.DeleteRows(1, nRows-1);
   }

   SendMessage(m_hStackList, CB_RESETCONTENT, 0, 0);
   ClearGrid();
}

void CInfoWindow::Enable(bool bEnable)
{
   EnableWindow(m_hStackList, bEnable);
   EnableWindow(m_gdVariables.m_hWnd, bEnable);
   m_nCurRtnStartingLineNo = 0;
   m_nCurRtnEndingLineNo = 0;
}

void CInfoWindow::GetCurRtnLineNoRange(int* pnStart, int* pnEnd)
{
   if (pnStart) {
      *pnStart = m_nCurRtnStartingLineNo;
   }
   if (pnEnd) {
      *pnEnd = m_nCurRtnEndingLineNo;
   }
}

int CInfoWindow::GetVaribleValueInCurRtn(LPCTSTR pVarWanted, TCHAR* buf, size_t size)
{
   assert (buf);
   assert (size);

   if (m_nStackIdx < 0) {
      return -1;
   }

   LPCTSTR pVarName;
   value_t* pVal;
   int i = 1;
   var_handle_t hvar = debug_get_first_variable(m_hexe, m_nStackIdx, &pVarName, &pVal);
   while (hvar) {
      if (_tcscmp (pVarName, pVarWanted) != 0) {
         hvar = debug_get_next_variable(hvar, &pVarName, &pVal);
         continue;
      }

      buf[size-1] = _T('\0');
      if (pVal->type == NumericVal) {
         _sntprintf (buf, size, _T("%g"), pVal->val);
      } else if (pVal->type = StringVal) {
         _sntprintf (buf, size, _T("\"%s\""), pVal->str);
      } else {
         _sntprintf (buf, size, _T("%s"), _T("NOT INITIALIZED"));
      }
      return 0;
   }
   return -1;
}

#define VAR_COLS  3
char* g_pszHeader[] = {_T("Name"), _T("Type"), _T("Value")};
int g_nColWidth[] = {100, 100, 150};
#define DIVIDER_WIDTH   3

LRESULT CInfoWindow::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
   m_hStackList = ::CreateWindow(
               _T("COMBOBOX"),
               NULL,
               WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST |WS_VSCROLL ,
               0, 0,
               10, 100,
               m_hWnd,
               (HMENU)STACKLIST_ID,
               _Module.GetModuleInstance(),
               NULL);

   RECT rc = {0, 0, 10, 10};
   m_gdVariables.SetCols(VAR_COLS);
   m_gdVariables.CreateGridCtrl(m_hWnd, rc, WS_VISIBLE, WS_EX_CLIENTEDGE, VARIABLEGRID_ID);
  
   for (int i = 0; i < VAR_COLS; i ++) {
      m_gdVariables.SetString(0, i, g_pszHeader[i]);
      m_gdVariables.SetColWidth(i, g_nColWidth[i]);
   }
   LayOut();
   return 0;
}

LRESULT CInfoWindow::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    return 0;
}

LRESULT CInfoWindow::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
   LayOut();
   return 0;
}

#define STR_VAL_TYPE_STRING   _T("String Type")
#define STR_VAL_TYPE_NUMERIC  _T("Numeric Type")
#define STR_VAL_TYPE_UNINIT   _T("Not Initialized")

LRESULT CInfoWindow::OnStackSel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
   ClearGrid();
   int nIdx = SendMessage (m_hStackList, CB_GETCURSEL, 0, 0);
   int nTotal = SendMessage (m_hStackList, CB_GETCOUNT, 0, 0);
   m_nStackIdx = nTotal - 1 - nIdx;
   
   LPCTSTR pVarName;
   value_t* pVal;
   int i = 1;
   var_handle_t hvar = debug_get_first_variable(m_hexe, m_nStackIdx, &pVarName, &pVal);
   while (hvar) {
      m_gdVariables.InsertRows(i, 1);
      m_gdVariables.SetString(i, 0, pVarName);
      m_gdVariables.SetString(i, 1, GetTypeString(pVal->type));
      if (pVal->type == NumericVal) {
         TCHAR buf[BUFLEN];
         buf[BUFLEN-1] = _T('\0');
         _sntprintf (buf, BUFLEN, _T("%g"), pVal->val);
         m_gdVariables.SetString(i, 2, buf);
      } else if (pVal->type = StringVal) {
         m_gdVariables.SetString(i, 2, pVal->str);
      }
      hvar = debug_get_next_variable(hvar, &pVarName, &pVal);
      i++;
   }

   debug_get_routine_lineno_range (m_hexe, m_nStackIdx, &m_nCurRtnStartingLineNo, &m_nCurRtnEndingLineNo);
   _TRACE(_T("Starting from %d ending to %d.\n"), m_nCurRtnStartingLineNo, m_nCurRtnEndingLineNo);
   return 0;
}

void CInfoWindow::LayOut()
{
   assert (::IsWindow(m_hWnd));
   
   RECT rc, rcList;
   GetClientRect(&rc);
   ::MoveWindow(m_hStackList, rc.left, rc.top, rc.right - rc.left, 100, TRUE);

   ::GetWindowRect(m_hStackList, &rcList);
   ScreenToClient(&rcList);
   rc.top = rcList.bottom + DIVIDER_WIDTH;
   if (rc.top > rc.bottom) {
      // Not visible anyway.
      rc.bottom = rc.top + 100;
   }
   ::MoveWindow(m_gdVariables.m_hWnd, rc.left, rc.top, rc.right-rc.left, rc.bottom - rc.top, TRUE);
   return;
}

void CInfoWindow::AddStackItem(LPCTSTR rtn_name, int lineno)
{
   assert (::IsWindow(m_hWnd));
   assert (rtn_name);

   TCHAR szTxt[BUFLEN];
   szTxt[BUFLEN-1] = _T('\0');
   _sntprintf (szTxt, BUFLEN, _T("L%d    %s"), lineno, rtn_name);
   ::SendMessage(m_hStackList, CB_ADDSTRING, 0, (LPARAM)szTxt);
}

void CInfoWindow::ClearGrid()
{
   int nRows = m_gdVariables.GetRows();
   if (nRows > 1) {
      m_gdVariables.DeleteRows(1, nRows-1);
   }
}


LRESULT CInfoWindow::OnGridCellDblClk(UINT msg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
   return EditGrid(lParam);
}

LRESULT CInfoWindow::OnGridCellEndEditing(UINT msg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
   int nRow, nCol;
   if (HIWORD(wParam) == END_WITH_CANCEL) {
      return 0;
   }

   m_gdVariables.GetCurCell(&nRow, &nCol);
   assert (nCol == 2);

   value_type_t type = GetTypeFromString(m_gdVariables.GetString(nRow, 1));
   assert (type != NoneVal);
   
   const char* name = m_gdVariables.GetString(nRow, 0);
   int nIdx = SendMessage (m_hStackList, CB_GETCURSEL, 0, 0);
   int nTotal = SendMessage (m_hStackList, CB_GETCOUNT, 0, 0);
   int nStackIdx = nTotal - 1 - nIdx;
   
   var_handle_t var = debug_find_varriable_by_string (m_hexe, nStackIdx, name);
   if (type == NumericVal) {
      double d;
      int ret = _stscanf((LPCTSTR)lParam, _T("%lf"), &d);
      if (ret != 1) {
         ::MessageBox(m_hWnd, _T("This is NOT a numeric value!"), _T("Value type error"), MB_OK|MB_ICONSTOP);
         return 0;
      }
      debug_change_variable_numeric_value(var, d);
      m_gdVariables.SetString(nRow, nCol, (LPCTSTR) lParam); 
      return 0;
   }
   
   debug_change_variable_string_value(var, (LPCTSTR)lParam);
   m_gdVariables.SetString(nRow, nCol, (LPCTSTR) lParam); 
   return 0;
}

LRESULT CInfoWindow::OnGridEnterPressed(UINT msg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
   return EditGrid(lParam);
}

LRESULT CInfoWindow::EditGrid(LPARAM lParam)
{
   int nCol = LOWORD(lParam);

   if (nCol == 2) {
      int nRow = HIWORD(lParam);
      value_type_t type = GetTypeFromString(m_gdVariables.GetString(nRow, 1));
      if (type == NoneVal) {
         return 0;
      }
      m_gdVariables.EditCurCell();
   }
   return 0;
}

static LPCTSTR GetTypeString(value_type_t type)
{
   switch (type) {
   case NoneVal:
      return STR_VAL_TYPE_UNINIT;
   case NumericVal:
      return STR_VAL_TYPE_NUMERIC;
   case StringVal:
      return STR_VAL_TYPE_STRING;
   }
   return NULL;
}

static value_type_t GetTypeFromString(LPCTSTR str)
{
   if (_tcscmp(str, STR_VAL_TYPE_NUMERIC) == 0 ) {
      return NumericVal;
   }
   if (_tcscmp(str, STR_VAL_TYPE_STRING) == 0 ) {
      return StringVal;
   }
   return NoneVal;
}